```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Redis数据结构详解 | 技术小馆</title>
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        :root {
            --primary: #4f46e5;
            --primary-light: #6366f1;
            --secondary: #10b981;
            --dark: #1e293b;
            --light: #f8fafc;
            --gray: #64748b;
            --code-bg: #f1f5f9;
        }
        body {
            font-family: 'Noto Sans SC', 'Noto Serif SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-serif;
            color: #334155;
            line-height: 1.6;
        }
        .hero {
            background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
        }
        .section-title {
            position: relative;
            padding-left: 1.5rem;
        }
        .section-title::before {
            content: '';
            position: absolute;
            left: 0;
            top: 0.5rem;
            height: 1.5rem;
            width: 0.5rem;
            background: var(--primary);
            border-radius: 0.25rem;
        }
        .card {
            transition: all 0.3s ease;
            border-radius: 0.5rem;
            overflow: hidden;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
        }
        .card:hover {
            transform: translateY(-4px);
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
        }
        .code-block {
            background-color: var(--code-bg);
            border-radius: 0.5rem;
            font-family: 'Courier New', Courier, monospace;
        }
        .list-item {
            position: relative;
            padding-left: 1.75rem;
        }
        .list-item::before {
            content: '●';
            position: absolute;
            left: 0.5rem;
            color: var(--primary);
        }
        .footer {
            background-color: var(--dark);
        }
        img {
            border-radius: 0.5rem;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            transition: all 0.3s ease;
        }
        img:hover {
            transform: scale(1.02);
        }
    </style>
</head>
<body class="bg-gray-50">
    <!-- Hero Section -->
    <section class="hero text-white py-20 px-4">
        <div class="container mx-auto max-w-6xl">
            <div class="flex flex-col items-center text-center">
                <h1 class="text-4xl md:text-5xl font-bold mb-4">Redis 数据结构解析</h1>
                <p class="text-xl md:text-2xl opacity-90 max-w-3xl">
                    深入理解Redis内部数据结构的实现原理与设计思想
                </p>
                <div class="mt-8 flex space-x-4">
                    <a href="#redisobject" class="px-6 py-3 bg-white text-indigo-600 font-semibold rounded-lg shadow-md hover:bg-opacity-90 transition">开始探索</a>
                </div>
            </div>
        </div>
    </section>

    <!-- Main Content -->
    <div class="container mx-auto max-w-6xl px-4 py-12">
        <!-- RedisObject Section -->
        <section id="redisobject" class="mb-20">
            <h2 class="section-title text-3xl font-bold mb-8 text-gray-800">redisObject</h2>
            <div class="grid md:grid-cols-2 gap-8 items-start">
                <div>
                    <p class="mb-6 text-gray-700">Redis内部使用一个 redisObject对象来表示所有的key和value，redisObject的信息如下图所示：</p>
                    <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1660477239323-44fefe12-5748-4732-9db3-19bef48d2a74.png?x-oss-process=image%2Fresize%2Cw_937%2Climit_0" alt="redisObject结构" class="w-full">
                </div>
                <div>
                    <div class="card bg-white p-6 mb-6">
                        <h3 class="text-xl font-semibold mb-4 text-indigo-600">redisObject组成</h3>
                        <ul class="space-y-3">
                            <li class="list-item"><span class="font-semibold">type:</span> 用来表示这个redisObject是属于五种类型(string、hash、list、set、zset)的哪一种</li>
                            <li class="list-item"><span class="font-semibold">encoding:</span> 用来表示type的底层数据结构是用什么实现的</li>
                            <li class="list-item"><span class="font-semibold">ptr:</span> 指向底层数据结构的指针</li>
                            <li class="list-item"><span class="font-semibold">vm:</span> 只有打开了Redis的虚拟内存功能，此字段才会真正的分配内存，该功能默认是关闭状态的</li>
                        </ul>
                    </div>
                </div>
            </div>
        </section>

        <!-- String Section -->
        <section id="string" class="mb-20">
            <h2 class="section-title text-3xl font-bold mb-8 text-gray-800">String</h2>
            <p class="mb-6 text-gray-700">string使用一种叫动态字符串（SDS）的数据类型来实现。</p>
            
            <div class="grid md:grid-cols-2 gap-8 mb-10">
                <div>
                    <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1660475813297-be414727-74e1-4e0e-bfbf-c3e475f0de19.png" alt="SDS存储示例" class="w-full">
                </div>
                <div>
                    <h3 class="text-xl font-semibold mb-4 text-indigo-600">字符串的编码方式</h3>
                    <div class="space-y-6">
                        <div class="card bg-white p-6">
                            <h4 class="font-semibold text-lg mb-2 text-green-600">1. int</h4>
                            <p class="text-gray-700">如果一个字符串保存的是整数值，如 set number 10086，那么这个整数值可以用 long 类型标识。那么该字符串的 redisObject会把10086这个数值保存在ptr属性中，并将encoding设置成int。</p>
                            <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1678892439225-67903e18-d9a0-4adf-9b60-0317f7b1897c.png" alt="int编码示例" class="w-full mt-4">
                        </div>
                    </div>
                </div>
            </div>

            <div class="grid md:grid-cols-2 gap-8 mb-10">
                <div class="card bg-white p-6">
                    <h4 class="font-semibold text-lg mb-2 text-blue-600">2. raw</h4>
                    <p class="text-gray-700">如果字符串保存的是一个字符串值。并且这个字符串大于39个字节，那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值，内存分配次数为两次，并将redisObject的encoding设置为raw。</p>
                    <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1678892464141-24d3b306-3a2a-4a34-9bd6-270d55ebc63f.png" alt="raw编码示例" class="w-full mt-4">
                </div>
                <div class="card bg-white p-6">
                    <h4 class="font-semibold text-lg mb-2 text-purple-600">3. embstr</h4>
                    <p class="text-gray-700">如果字符串保存的是一个字符串值，并且这个字符串小于39个字节，那么字符串将使用embstr编码的方式来保存这个字符串。用embstr存储的话，一般字符串的存储内存很小，因此redis一次性分配redisObject和sds的内存，且内存连续。</p>
                    <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1678892477041-b3fe2ba8-e429-4c23-9908-e33ab44431ff.png" alt="embstr编码示例" class="w-full mt-4">
                </div>
            </div>

            <div class="card bg-white p-6">
                <h3 class="text-xl font-semibold mb-4 text-indigo-600">字符串对象总结</h3>
                <ul class="space-y-3">
                    <li class="list-item">在Redis中，存储long、double类型的浮点数是先转换为字符串再进行存储的</li>
                    <li class="list-item">raw与embstr编码效果是相同的，不同在于内存分配与释放，raw两次，embstr一次</li>
                    <li class="list-item">embstr内存块连续，能更好的利用缓存在来的优势</li>
                    <li class="list-item">int编码和embstr编码如果做追加字符串等操作，满足条件下会被转换为raw编码；embstr编码的对象是只读的，一旦修改会先转码到raw</li>
                </ul>
            </div>
        </section>

        <!-- Hash Section -->
        <section id="hash" class="mb-20">
            <h2 class="section-title text-3xl font-bold mb-8 text-gray-800">Hash</h2>
            <p class="mb-6 text-gray-700">hash使用ziplist或hashtable结构实现</p>

            <div class="grid md:grid-cols-2 gap-8 mb-10">
                <div class="card bg-white p-6">
                    <h3 class="text-xl font-semibold mb-4 text-indigo-600">1. ziplist</h3>
                    <p class="text-gray-700 mb-4">当哈希对象保存的键值对数量少于512，且所有键值对的长度都少于64字节时，使用压缩列表保存。</p>
                    <p class="text-gray-700">在压缩列表中，每当有新的键值对要加入到哈希对象时，程序会先将保存了键的压缩列表节点推入到压缩列表表尾，然后再将保存了值的压缩列表节点推入到压缩列表表尾，因此：保存了同一键值对的两个节点总是紧挨在一起。</p>
                    <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1660478303294-c1f2d561-e38c-4e0a-a027-04356cc2559f.png" alt="ziplist存储示例" class="w-full mt-4">
                </div>
                <div class="card bg-white p-6">
                    <h3 class="text-xl font-semibold mb-4 text-indigo-600">2. hashtable</h3>
                    <p class="text-gray-700 mb-4">若哈希对象保存的键值对个数大于512，并且其中有键值对大于64个字节，就使用hashtable保存。</p>
                    <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1678894352560-5d219c1d-943f-4931-9015-e263c967ab14.png" alt="hashtable存储示例" class="w-full mt-4">
                    <p class="text-gray-700 mt-4">可以看到，哈希表是一个数组（dictEntry **table），数组的每个元素是链表(dictEntry)。</p>
                </div>
            </div>
        </section>

        <!-- List Section -->
        <section id="list" class="mb-20">
            <h2 class="section-title text-3xl font-bold mb-8 text-gray-800">List</h2>
            <p class="mb-6 text-gray-700">list使用链表来实现。</p>
            <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1660476157516-37e858d8-28ae-47fe-b87f-835483b34849.png?x-oss-process=image%2Fresize%2Cw_937%2Climit_0" alt="list结构" class="w-full mb-8">

            <div class="grid md:grid-cols-2 gap-8 mb-10">
                <div>
                    <h3 class="text-xl font-semibold mb-4 text-indigo-600">实现方式</h3>
                    <ul class="space-y-3 mb-6">
                        <li class="list-item"><span class="font-semibold">压缩列表ziplist</span></li>
                        <li class="list-item"><span class="font-semibold">双向链表linkedlist</span></li>
                    </ul>
                    <p class="text-gray-700">双向链表占用的内存比压缩列表要多，所以当创建新的列表键时，列表会优先考虑使用压缩列表，并且在有需要的时候，才从压缩列表实现转换到双向链表实现。</p>
                </div>
                <div class="card bg-white p-6">
                    <h3 class="text-xl font-semibold mb-4 text-indigo-600">用ziplist列表的实现</h3>
                    <p class="text-gray-700 mb-4">压缩列表 ziplist 是为 Redis 节约内存而开发的。ziplist 是由一系列特殊编码的内存块构成的列表(像内存连续的数组，但每个元素长度不同)， 一个 ziplist 可以包含多个节点（entry）。</p>
                    <p class="text-gray-700">当元素个数较少时，Redis 用 ziplist 来存储数据，当元素个数超过某个值时，链表键中会把 ziplist 转化为 linkedlist。由于内存是连续分配的，所以遍历速度很快。</p>
                    <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1678893791077-636ea15a-93ae-4f3e-a009-90d7cc86a123.png" alt="ziplist结构" class="w-full mt-4">
                </div>
            </div>

            <div class="grid md:grid-cols-2 gap-8 mb-10">
                <div>
                    <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1678893676215-2345c339-bd72-45f4-9b86-ea5b09405a8f.png" alt="ziplist详细结构" class="w-full mb-4">
                    <ul class="space-y-2">
                        <li class="list-item"><span class="font-semibold">zlbytes:</span> 记录整个ziplist的大小</li>
                        <li class="list-item"><span class="font-semibold">zltail:</span> ziplist开始指针与最后一个entry之间的偏移量</li>
                        <li class="list-item"><span class="font-semibold">zllen:</span> entry数量</li>
                        <li class="list-item"><span class="font-semibold">entry:</span> 存储具体数据的节点</li>
                        <li class="list-item"><span class="font-semibold">zlend:</span> ziplist结尾标识</li>
                    </ul>
                </div>
                <div>
                    <h4 class="text-lg font-semibold mb-3 text-indigo-600">entry结构</h4>
                    <p class="text-gray-700 mb-4">每一个entry由以下三个部分组成。</p>
                    <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1678893716151-3232843e-6138-4942-a4bd-784065c7b149.png" alt="entry结构" class="w-full mb-4">
                    <ul class="space-y-2">
                        <li class="list-item"><span class="font-semibold">previous_entry_length:</span> 上一个entry的大小</li>
                        <li class="list-item"><span class="font-semibold">encoding:</span> 记录content的类型以及长度</li>
                        <li class="list-item"><span class="font-semibold">content:</span> 一个整形或者字节数组</li>
                    </ul>
                </div>
            </div>

            <div class="card bg-white p-6">
                <h3 class="text-xl font-semibold mb-4 text-indigo-600">用linkedlist列表的实现</h3>
                <p class="text-gray-700 mb-4">当列表长度少于512且每个元素都少于64个字节，那么就用ziplist存储。否则就用linkedlist存储。</p>
                <p class="text-gray-700 mb-4">linkedlist是标准的双向链表，Node节点包含prev和next指针，可以进行双向遍历。</p>
                <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1660476795874-752b6ed1-739e-47ab-9167-fcfc72cbe9eb.png" alt="linkedlist节点结构" class="w-full mb-4">
                <p class="text-gray-700 mb-4">拥有4个节点的linkedlist示意图如下:</p>
                <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1678893879663-b16c9b11-c619-497f-8f73-7f76ea27aa28.png" alt="linkedlist示例" class="w-full">
            </div>
        </section>

        <!-- Set Section -->
        <section id="set" class="mb-20">
            <h2 class="section-title text-3xl font-bold mb-8 text-gray-800">Set</h2>
            <p class="mb-6 text-gray-700">set使用intset、hashtable来实现</p>

            <div class="grid md:grid-cols-2 gap-8">
                <div class="card bg-white p-6">
                    <h3 class="text-xl font-semibold mb-4 text-indigo-600">1. intset</h3>
                    <p class="text-gray-700">当集合的长度少于512时，并且所有元素都是整数，使用intset存储。否则使用hashtable。</p>
                    <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1660478682854-dca62f33-c6ce-4420-8f55-6551a3bc438e.png?x-oss-process=image%2Fresize%2Cw_937%2Climit_0" alt="intset结构" class="w-full mt-4">
                </div>
                <div class="card bg-white p-6">
                    <h3 class="text-xl font-semibold mb-4 text-indigo-600">2. hashtable</h3>
                    <p class="text-gray-700">hashtable编码的底层实现是字典，字典的每个键是字符串对象，只不过值都是空(NULL)。</p>
                    <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1660478727088-f30ce37b-9c24-4832-8584-4db728f35028.png?x-oss-process=image%2Fresize%2Cw_937%2Climit_0" alt="hashtable结构" class="w-full mt-4">
                </div>
            </div>
        </section>

        <!-- ZSet Section -->
        <section id="zset" class="mb-20">
            <h2 class="section-title text-3xl font-bold mb-8 text-gray-800">ZSet</h2>
            <p class="mb-6 text-gray-700">zset 使用ziplist或skiplist实现</p>

            <div class="grid md:grid-cols-2 gap-8 mb-10">
                <div class="card bg-white p-6">
                    <h3 class="text-xl font-semibold mb-4 text-indigo-600">1. ziplist</h3>
                    <p class="text-gray-700">当zset的长度少于128，并且所有元素的长度都少于64字节时，用ziplist存储。我们可以看到，每个节点，前面是字符串，后面是分数值。分值小的靠近表头，大的靠近表尾。</p>
                    <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1660478886600-3b6947f9-1870-4457-8c92-cf77a94d266c.png" alt="ziplist存储zset" class="w-full mt-4">
                </div>
                <div class="card bg-white p-6">
                    <h3 class="text-xl font-semibold mb-4 text-indigo-600">2. skiplist</h3>
                    <p class="text-gray-700">redis 的 skiplist 是由字典dict 和跳表构成的。</p>
                    <ul class="space-y-2 mb-4">
                        <li class="list-item">dict 用于记录字符串对象和分数，即查询字符串对象对应分数</li>
                        <li class="list-item">跳表则用来，根据分数查询对应字符串</li>
                    </ul>
                    <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1660479174602-612a5b96-9893-474b-ab83-a7128a20cbee.png?x-oss-process=image%2Fresize%2Cw_937%2Climit_0" alt="skiplist结构" class="w-full mt-2">
                </div>
            </div>

            <div class="card bg-white p-6">
                <h3 class="text-xl font-semibold mb-4 text-indigo-600">zskiplist 结构</h3>
                <p class="text-gray-700">上图最左边的是 zskiplist 结构，该结构包含以下属性：</p>
                <ul class="space-y-2 mt-3">
                    <li class="list-item"><span class="font-semibold">header:</span> 指向跳跃表的表头节点</li>
                    <li class="list-item"><span class="font-semibold">tail:</span> 指向跳跃表的表尾节点</li>
                    <li class="list-item"><span class="font-semibold">level:</span> 记录目前跳跃表内，层数最大的那个节点层数（表头节点的层数不计算在内）</li>
                    <li class="list-item"><span class="font-semibold">length:</span> 记录跳跃表的长度，也就是跳跃表目前包含节点的数量（表头节点不计算在内）</li>
                </ul>
            </div>
        </section>

        <!-- Summary Section -->
        <section id="summary" class="mb-20">
            <h2 class="section-title text-3xl font-bold mb-8 text-gray-800">总结</h2>
            <div class="card bg-white p-6 code-block">
                <pre class="overflow-x-auto p-4"><code class="text-gray-800">
1、string：动态字符串SDS
    a)int:字符串类型的整数，底层使用int实现，比如 set phone 10086
    b)raw:动态字符串，大于39个字节
    c)embstr:动态字符串，小于39个字节，内存的都是连续的饿，速度较快，只需要分配一次
2、list：链表
    a)ziplist：列表的长度小于512，元素的字节数小于64使用ziplist，它是进行了压缩
    b)linkedList：不满足上面的条件就是用linkedList，双向链表
3、set:intset或者hashtable
    a)intset:集合长度小于512且元素都是整数，使用intset实现
    b)hashtabel:hashtable底层使用字典结构(hash)，value都是null值
4、hash：ziplist或者hashtable
    a)键值对的数量小于512,所有键值的长度小于64字节，使用ziplist进行压缩
    b)键值对的数量大于512,所有键值的长度但于64字节，使用hashtable
5、zset:ziplist和skiplist
    a)集合长度小于128，元素的字节数小于64使用ziplist压缩
    b)上面条件不满足就是用skiplist，底层利用跳表实现
                </code></pre>
            </div>
        </section>
    </div>

    <!-- Footer -->
    <footer class="footer text-white py-8">
        <div class="container mx-auto max-w-6xl px-4">
            <div class="flex flex-col md:flex-row justify-between items-center">
                <div class="mb-4 md:mb-0">
                    <p class="font-semibold">技术小馆</p>
                </div>
                <div>
                    <a href="http://www.yuque.com/jtostring" class="hover:text-indigo-300 transition">http://www.yuque.com/jtostring</a>
                </div>
            </div>
        </div>
    </footer>

    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            flowchart: {
                useMaxWidth: true,
                htmlLabels: true
            }
        });
    </script>
</body>
</html>
```